using System;
using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
using System.IO.Packaging;
using System.ComponentModel;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Security.Cryptography.Xml;
using System.Xml;

namespace OWA.Activities
{
    [ActivityValidator(typeof(SignPackageActivityValidator))]
	public class SignPackageActivity
        : AbstractPackageActivity
    {
        static readonly string RT_OfficeDocument =
            "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
        static readonly string XMLNS_Office = "urn:schemas-microsoft-com:office:office";
        static readonly string XMLNS_Vml = "urn:schemas-microsoft-com:vml";

        StoreLocation _storeLocation;        

        public static DependencyProperty CertificateProperty = 
            DependencyProperty.Register("Certificate", 
                typeof(X509Certificate2), typeof(SignPackageActivity));
        public static DependencyProperty SigningNameProperty = 
            DependencyProperty.Register("SigningName", 
                typeof(string), typeof(SignPackageActivity));
        public static DependencyProperty SignatureLineIDProperty = 
            DependencyProperty.Register("SignatureLineID", 
                typeof(string), typeof(SignPackageActivity));

        [DefaultValue(StoreLocation.CurrentUser)]
        [Description("The store to retrieve a certificate from for signing the package. Used when Certificate is not specified.")]
        [Category("Certificate")]
        public StoreLocation StoreLocation
        {
            get { return Enum.IsDefined(typeof(StoreLocation), _storeLocation) ? _storeLocation : StoreLocation.CurrentUser ; }
            set { _storeLocation = value; }
        }

        [Description("The certificate used to sign the package.")]
        [Category("Certificate")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public X509Certificate2 Certificate
        {
            get
            {
                return ((X509Certificate2)(base.GetValue(SignPackageActivity.CertificateProperty)));
            }
            set
            {
                base.SetValue(SignPackageActivity.CertificateProperty, value);
            }
        }

        [Description("The name of the one signing the package.")]
        [Category("Signature Details")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string SigningName
        {
            get
            {
                return ((string)(base.GetValue(SignPackageActivity.SigningNameProperty)));
            }
            set
            {
                base.SetValue(SignPackageActivity.SigningNameProperty, value);
            }
        }

        [Description("The ID value of the signature line UI, if available.")]
        [Category("Signature Details")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public string SignatureLineID
        {
            get
            {
                return ((string)(base.GetValue(SignPackageActivity.SignatureLineIDProperty)));
            }
            set
            {
                base.SetValue(SignPackageActivity.SignatureLineIDProperty, value);
            }
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            Package package = GetPackage();
            X509Certificate2 certificate = GetCertificate();
            if (certificate == null)
            {
                string message = String.Format(
                    "No certificate was found in the {0} store", StoreLocation);
                throw new PackageSigningException(message);
            }
            SignPackage(package, certificate);
            return ActivityExecutionStatus.Closed;
        }

        X509Certificate2 GetCertificate()
        {
            X509Certificate2 certificate = Certificate;
            if (certificate == null)
            {
                X509Store certificateStore =
                    new X509Store(StoreLocation);
                certificateStore.Open(OpenFlags.ReadOnly);
                
                X509Certificate2Collection certificates = 
                    certificateStore.Certificates.Find(X509FindType.FindByKeyUsage,
                        X509KeyUsageFlags.DigitalSignature, true);
                if (certificates.Count > 0)
                {
                    certificate = certificates[0];
                }
            }
            return certificate;
        }
    
        void SignPackage(Package package, X509Certificate2 certificate)
        {
            List<Uri> partsToSign = new List<Uri>();
            List<PackageRelationshipSelector> relationshipsToSign =
                new List<PackageRelationshipSelector>();
            List<Uri> finishedItems = new List<Uri>();

            partsToSign.Add(new Uri("/docProps/core.xml", UriKind.Relative)); 
            foreach (PackageRelationship relationship in
                package.GetRelationshipsByType(RT_OfficeDocument))
            {
                AddSignableItems(relationship,
                    partsToSign, relationshipsToSign);
            }

            string signatureID = "idPackageSignature";
            DataObject officeObject = CreateOfficeObject();
            Reference officeObjectReference = new Reference("#idOfficeObject");

            PackageDigitalSignatureManager mgr =
                new PackageDigitalSignatureManager(package);
            mgr.CertificateOption = CertificateEmbeddingOption.InSignaturePart;
            mgr.Sign(partsToSign, certificate,
                relationshipsToSign, signatureID,
                new DataObject[] { officeObject },
                new Reference[] { officeObjectReference });
        }

        static void AddSignableItems(
            PackageRelationship relationship,
            List<Uri> partsToSign,
            List<PackageRelationshipSelector> relationshipsToSign)
        {
            PackageRelationshipSelector selector =
                new PackageRelationshipSelector(
                    relationship.SourceUri,
                    PackageRelationshipSelectorType.Id,
                    relationship.Id);
            relationshipsToSign.Add(selector);
            if (relationship.TargetMode == TargetMode.Internal)
            {
                PackagePart part = relationship.Package.GetPart(
                    PackUriHelper.ResolvePartUri(
                        relationship.SourceUri, relationship.TargetUri));
                if (partsToSign.Contains(part.Uri) == false)
                {
                    partsToSign.Add(part.Uri);
                    foreach (PackageRelationship childRelationship in
                        part.GetRelationships())
                    {
                        AddSignableItems(childRelationship,
                            partsToSign, relationshipsToSign);
                    }
                }
            }
        }

        DataObject CreateOfficeObject()
        {
            XmlDocument document = new XmlDocument();
            document.LoadXml(Properties.Resources.OfficeObject);
            XmlNamespaceManager mgr = new XmlNamespaceManager(document.NameTable);
            mgr.AddNamespace("x", "http://www.w3.org/2000/09/xmldsig#");
            mgr.AddNamespace("o", "http://schemas.microsoft.com/office/2006/digsig");

            if (String.IsNullOrEmpty(SignatureLineID) == false)
            {
                string xpath = "/x:SignatureProperties/x:SignatureProperty/o:SignatureInfoV1/o:SetupID";
                document.SelectSingleNode(xpath, mgr).InnerText = SignatureLineID ;
            }
            if(String.IsNullOrEmpty(SigningName) == false)
            {
                string xpath = "/x:SignatureProperties/x:SignatureProperty/o:SignatureInfoV1/o:SignatureText";
                document.SelectSingleNode(xpath, mgr).InnerText = SigningName;
            }
            DataObject officeObject = new DataObject();
            // do not change the order of the following two lines
            officeObject.LoadXml(document.DocumentElement); // resets ID
            officeObject.Id = "idOfficeObject"; // required ID, do not change
            return officeObject;
        }
	}
}
